// map.c++
// map class implementation file

#include <iostream>
#include <fstream>
#include <SDL/SDL.h>
#include <math.h>
#include <assert.h>

#include "highground.h"
#include "map.h"
#ifdef CLIENT
#include "gfx.h"
#endif //CLIENT

Map::Map() {
	initialised_ = 0;
	width_ = 0;
	height_ = 0;
	#ifdef CLIENT
	char fn[128];
	whiteMask_ = loadBitmap("images/white.bmp");
	grass_ = loadBitmap("images/grass.bmp");
	//water_ = loadBitmap("images/water_001.bmp");
	for (unsigned int i = 1; i <= 32; i++) {
		sprintf(fn, "images/water_%03d.bmp", i);
		water_[i - 1] = loadBitmap(fn);	
	}	
	waterFrame_ = 0;
	#endif //CLIENT
	map_ = NULL;
}

Map::~Map() {
	uninitialise();
	#ifdef CLIENT
	SDL_FreeSurface(whiteMask_);
	SDL_FreeSurface(grass_);
	for (unsigned int i = 1; i <= 32; i++) {
		SDL_FreeSurface(water_[i - 1]);
	}	
	#endif //CLIENT
}

void Map::setSize(const unsigned int width, const unsigned int height) {
	width_ = width;
	height_ = height;
}

// TODO for malloc() fail.
void Map::initialise() { 
	// map_ = (unsigned char***)malloc(sizeof(unsigned char**) * MAP_LAYERS);
	map_ = new unsigned char** [MAP_LAYERS];
	for (unsigned int layer = 0; layer < MAP_LAYERS; ++layer) {
		// map_[layer] = (unsigned char**)malloc(sizeof(unsigned char*) * width_);
		map_[layer] = new unsigned char* [width_];
		for (unsigned int x = 0; x < width_; ++x) {
			// map_[layer][x] = (unsigned char*)malloc(sizeof(unsigned char) * height_);
			map_[layer][x] = new unsigned char [height_];
			for (unsigned int y = 0; y < height_; ++y) {
				map_[layer][x][y] = 0;
			}
		}
	}
	initialised_ = 1;
}

void Map::uninitialise() {
	if (initialised_ == 1) {
		for (unsigned int layer = 0; layer < MAP_LAYERS; ++layer) {
			for (unsigned int x = 0; x < width_; ++x) {
				// free(map_[layer][x]);
				delete [] map_[layer][x];
			}
			// free(map_[layer]);
			delete [] map_[layer];
		}
		// free(map_);
		delete [] map_;
		initialised_ = 0;
	}
	width_ = 0;
	height_ = 0;
}

// Quite possibly broken, along with save(). Don't use at the moment.
bool Map::load(char *fn) {
	if(initialised_ == 1) {
		uninitialise();
	}
	std::cout << "Opening map file: " << fn << " ... ";
	std::ifstream mapFile(fn, std::ios::binary);
	if (!mapFile) {
		std::cout << "[FAIL]" << std::endl;
		return 1;
	}
	mapFile.read((char*)this, sizeof(*this));   
	initialise();
	for (unsigned int layer = 0; layer < MAP_LAYERS; ++layer) {
		for (unsigned int x = 0; x < width_; ++x) {
			for (unsigned int y = 0; y < height_; ++y) {
				mapFile >> map_[layer][x][y];
			}
		}
	}
	mapFile.close();
	std::cout << "[ OK ]" << std::endl;     
	return 0;
}

bool Map::save(char *fn) {
	std::cout << "Saving map file: " << fn << " ... ";
	std::ofstream mapFile(fn, std::ios::binary);
	if (!mapFile) {
		std::cout << "[FAIL]" << std::endl;
		return 1;
	}
	mapFile.write((char*)this, sizeof(*this));   
	for (unsigned int layer = 0; layer < MAP_LAYERS; ++layer) {
		for (unsigned int x = 0; x < width_; ++x) {
			for (unsigned int y = 0; y < height_; ++y) {
				mapFile << map_[layer][x][y];
			}
		}
	}
	mapFile.close();
	std::cout << "[ OK ]" << std::endl;          
	return 0;
}

unsigned int Map::getWidth() {
	return width_;
}

unsigned int Map::getHeight() {
	return height_;
}

unsigned char Map::getTile(const unsigned int layer, const unsigned int x,
                           const unsigned int y) {
	assert(x < width_);
	assert(y < height_);
	assert(layer < MAP_LAYERS);
	return map_[layer][x][y];
}

void Map::setTile(const unsigned int layer, const unsigned int x,
                  const unsigned int y, const unsigned char tile) {
	assert(x < width_);
	assert(y < height_);
	assert(layer < MAP_LAYERS);
	map_[layer][x][y] = tile;
}

void Map::drawEarth() {
	assert(whiteMask_ != NULL);
	assert(grass_ != NULL);
	if (initialised_ == 0)
		return;
	for (unsigned int y = 0; y < height_; ++y) {
		for (unsigned int x = 0; x < width_; ++x) {
			drawEarth(x, y);
		}
	}
}

void Map::drawEarth(const unsigned int x, const unsigned int y) {
	#ifdef CLIENT
	drawToTile(grass_, x, y);
	drawToTile(whiteMask_, x, y, map_[0][x][y] / 2);
	#endif
}

void Map::drawWater() {
	assert(water_ != NULL);
	for (unsigned int y = 0; y < height_; ++y) {
		for (unsigned int x = 0; x < width_; ++x) {
			drawWater(x, y);
		}
	}
}

void Map::drawWater(const unsigned int x, const unsigned int y) {
	#ifdef CLIENT
	if (map_[1][x][y] > 0) {
		drawToTile(water_[waterFrame_], x, y, 128);
	}
	#endif
}

// I wrote this code ages ago, I know it sucks.
void Map::subdivideDisplace(unsigned int maxHeight, const float jaggedness,
                            const unsigned int maxIterations) {
	srand(time(NULL));
	signed int randomHeight;
	unsigned int sign, xInterval, yInterval, line, point;
	unsigned int powerOfTwo = 1;
	std::cout << "Generating a random map... " << std::flush;
	for (unsigned int i = 0; i < maxIterations; ++i) {
		xInterval = width_ / powerOfTwo;
		yInterval = height_ / powerOfTwo;
		for (line = 0; line < powerOfTwo + 1; ++line) {
			for (point = 0; point < powerOfTwo; ++point) {
				randomHeight = (rand() % maxHeight) + (maxHeight / 2);
				sign = rand() % 2;
				if (sign != 0) {
					randomHeight = randomHeight * -1;
				}
				offsetArea((float)(xInterval * (point + 0.5)), yInterval * line,
				           width_ / powerOfTwo, randomHeight);
			}
		}
		for (line = 0; line < powerOfTwo; ++line) {
			for(point = 0; point < powerOfTwo + 1; ++point) {
				randomHeight = (rand() % maxHeight) + (maxHeight / 2);
				sign = rand() % 2;
				if (sign != 0) {
					randomHeight = randomHeight * -1;
				}
				offsetArea(xInterval * point,
				           (signed int)(yInterval * (line + 0.5)),
				           width_ / powerOfTwo, randomHeight);
			}
		}
		powerOfTwo = powerOfTwo * 2;
		maxHeight = (unsigned int)(maxHeight * jaggedness);
	}
	std::cout<<"Done."<<std::endl;
}

void Map::offsetArea(const float centreX, const float centreY,
                     const float radius, const float amount) {
	float distance;
	float offset;
	for (unsigned int y = 0; y < height_; ++y) {
		for (unsigned int x = 0; x < width_; ++x)	{
			distance = (unsigned int)sqrt(((centreX - x) * (centreX - x)) +
			                              ((centreY - y) * (centreY - y)));
			if (distance > radius) {
				distance = radius;
			}
			offset = (radius - distance) * (amount / radius);		
			if (offset > (255 - map_[0][x][y])) {
				map_[0][x][y] = ~0;
			} else if ((offset * -1) > map_[0][x][y]) {
				map_[0][x][y] = 0;
			} else {
				map_[0][x][y] = map_[0][x][y] + (signed char)offset;
			}
		}
	}
}

unsigned char*** Map::getMapData() {
	return map_;
}

void Map::nextFrame() {
	++waterFrame_;
	if (waterFrame_ > 31)
		waterFrame_ = 0;
}
